﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Xml.Linq;
using AdaptationProject.Properties;
using SharedTypes;

namespace AdaptationProject.Utils
{
    /// <summary>
    /// Класс отвечающий за анализ содержимого XML файлов
    /// согласно внутреннему формату.
    /// </summary>
    public class XMLParser : IXMLParser
    {
        #region Members
        /// <summary>
        /// Текущий загруженный ХМЛ документ.
        /// </summary>
        private XDocument currentDocument;

        /// <summary>
        /// Путь к текущему ХМЛ документу.
        /// </summary>
        private string currentPath;
        #endregion // Members

        #region Properties
        /// <summary>
        /// Свойство определяющее, загружен ли в парсер
        /// какой либо документ в данный момент.
        /// </summary>
        public bool IsLoaded { get; private set; }
        #endregion // Properties

        #region Constructor
        /// <summary>
        /// Конструктор.
        /// </summary>
        public XMLParser()
        {
            IsLoaded = false;
        }
        #endregion // Constructor

        #region Methods
        /// <summary>
        /// Загружает ХМЛ файл.
        /// </summary>
        /// <param name="path">Абсолютный путь к ХМЛ файлу.</param>
        /// <returns>Истина если загрузка произошла успешно,
        /// иначе - ложь.</returns>
        public bool Load(string path)
        {
            bool result = true;
            try
            {
                currentPath = path;
                currentDocument = XDocument.Load(path);
                IsLoaded = true;
            }
            catch
            {
                result = false;
            }
            return result;
        }

        /// <summary>
        /// Создаёт шаблон файла описания алгоритма.
        /// </summary>
        /// <param name="path">Абсолютный путь для размещения шаблона.</param>
        public void CreateTemplateDocument(string path)
        {
            if (ValidatePath(path))
            {
                CreateTemplateDoc(path);
            }
            else
            {
                MessageBox.Show(Resources.XMLParser_Incorrect_Extension);
            }
        }

        /// <summary>
        /// Сохраняет документ по текущему пути.
        /// </summary>
        public void Save()
        {
            SaveTo(currentPath);
        }

        /// <summary>
        /// Сохраняет документ.
        /// </summary>
        /// <param name="newPath">Абсолютный путь.</param>
        public void SaveTo(string newPath)
        {
            if (ValidatePath(newPath))
            {
                currentDocument.Save(newPath);
            }
            else
            {
                throw new Exception(Resources.XMLParser_Incorrect_Extension);
            }
        }

        /// <summary>
        /// Устанавливает заголовок файла описания алгоритма исполнительного файла.
        /// </summary>
        /// <param name="algorithmName">Имя алгоритма.</param>
        /// <param name="algorithmFullPath">Абсолютный путь к библиотеке, содержащей алгоритм.</param>
        /// <param name="exePath">Путь к исполняемому файлу, необходимому для функционирования алгоритма
        /// (может быть пустым).</param>
        public void SetHeader(string algorithmName, string algorithmFullPath, string exePath)
        {
            
            XElement[] newElements = {
                                     new XElement("AlgName", algorithmName),
                                     new XElement("AlgPath", algorithmFullPath),
                                     new XElement("ExePath", exePath) 
                                 };
            currentDocument.Descendants("Header").First().Add(newElements);
        }

        /// <summary>
        /// Добавляет параметр алгоритма исполнительного файла, подлежащий адаптации.
        /// </summary>
        /// <param name="orderNum">Порядковый номер параметра.</param>
        /// <param name="shortName">Имя параметра.</param>
        /// <param name="description">Описание назначение параметра.</param>
        /// <param name="acceptableRange">Допустимые значения 
        /// (строка, организованная согласно внутреннему формату). </param>
        /// <param name="defValue">Значение по умолчанию.</param>
        public void AddParameter(int orderNum, string shortName, string description, string acceptableRange, string defValue)
        {
            XElement newElements = new XElement("Parameter",
                                                new XElement("OrderNum", orderNum),
                                                new XElement("ShortName", shortName),
                                                new XElement("Description", description),
                                                new XElement("AcceptableRange", acceptableRange),
                                                new XElement("DefaultValue", defValue));
            currentDocument.Descendants("Parameters").First().Add(newElements);
        }

        /// <summary>
        /// Возвращает имя алгоритма, указанное в его файле описания.
        /// </summary>
        /// <returns>Имя алгоритма исполнительного файла.</returns>
        public string GetAlgorithmName()
        {
            string result = string.Empty;
            result = currentDocument.Descendants("AlgName").First().Value;
            return result;
        }

        /// <summary>
        /// Возвращает путь к библиотеке, содержащей алгоритм исполнительного файла. 
        /// </summary>
        /// <returns>Путь к библиотеке.</returns>
        public string GetAlgorithmPath()
        {
            string result = string.Empty;
            result = currentDocument.Descendants("AlgPath").First().Value;
            return result;
        }

        /// <summary>
        /// Возвращает путь к исполняемому файлу алгоритма исполнительного файла. 
        /// </summary>
        /// <returns>Путь к исполняемому файлу.</returns>
        public string GetExePath()
        {
            string result = string.Empty;
            result = currentDocument.Descendants("ExePath").First().Value;
            return result;
        }

        /// <summary>
        /// Загружает список адаптируемых параметров алгоритма исполнительного файла.
        /// </summary>
        /// <returns>Список адаптируемых параметров.</returns>
        public IDictionary<int, LinkedParameter> GetAlgorithmParameters()
        {
            IDictionary<int, LinkedParameter> toRet = new Dictionary<int, LinkedParameter>();
            var parameters = from param in currentDocument.Descendants("Parameter")
                             orderby Int32.Parse(param.Element("OrderNum").Value) ascending
                             select param;

            foreach (XElement parameter in parameters)
            {
                byte orderNum = byte.Parse(parameter.Element("OrderNum").Value);
                string shortName = parameter.Element("ShortName").Value;
                string description = parameter.Element("Description").Value;
                IList<object> values = GetListOfAcceptableValues(parameter.Element("AcceptableRange").Value);
                object defValue = parameter.Element("DefaultValue").Value;

                IList<AcceptableValue> acceptValues = new List<AcceptableValue>();
                AcceptableValue tempAcceptValue = new AcceptableValue();
                tempAcceptValue.values = values;
                tempAcceptValue.TextValues = "";
                tempAcceptValue.Percent = 0;
                acceptValues.Add(tempAcceptValue);

                IParameter param = new Parameter(orderNum, shortName, description, acceptValues, defValue);
                toRet.Add(byte.Parse(parameter.Element("OrderNum").Value),
                          new LinkedParameter(0, param));
            }
            return toRet;
        }

        /// <summary>
        /// Возвращает список допустимых значения параметра.
        /// </summary>
        /// <param name="rangedValues">Строка, согласованная с внутренним форматом.</param>
        /// <returns>Список допустимых значений параметра.</returns>
        private IList<object> GetListOfAcceptableValues(string rangedValues)
        {
            if (string.IsNullOrEmpty(rangedValues))
            {
                throw new ArgumentNullException(Resources.XMLParser_Range_Is_Empty);
            }
            IList<object> toRet = new List<object>();
            string[] values = rangedValues.Split(';');
            foreach (string value in values)
            {
                if (value.Contains('~'))
                {
                    AddRange(value, ref toRet);
                }
                else
                {
                    toRet.Add(value);
                }
            }
            return toRet;
        }

        /// <summary>
        /// Заполняет список значений параметра при обнаружении 
        /// интервала числовых значений.
        /// </summary>
        /// <param name="value">Интервальное значение.</param>
        /// <param name="toRet">Список допустимых состояний.</param>
        private void AddRange(string value, ref IList<object> toRet)
        {
            /*int leftBoundary, rightBoundary;
            string[] boundaryValues = value.Split('~');
            if (!Int32.TryParse(boundaryValues[0], out leftBoundary) || 
                !Int32.TryParse(boundaryValues[1], out rightBoundary))
            {
                throw new Exception(Resources.XMLParser_Range_Values_Must_Be_Digits);
            }
            */
            long leftBoundary, rightBoundary;
            string[] boundaryValues = value.Split('~');
            if (!Int64.TryParse(boundaryValues[0], out leftBoundary) ||
                !Int64.TryParse(boundaryValues[1], out rightBoundary))
            {
                throw new Exception(Resources.XMLParser_Range_Values_Must_Be_Digits);
            }
            if (leftBoundary > rightBoundary)
            {
                long tmp = rightBoundary;
                rightBoundary = leftBoundary;
                leftBoundary = tmp;
            }

            for (long i = leftBoundary; i <= rightBoundary; i++)
            {
                toRet.Add(i);
            }
        }

        /// <summary>
        /// Создаёт шаблон документа описания алгоритма.
        /// </summary>
        /// <param name="path">Путь к документу.</param>
        private void CreateTemplateDoc(string path)
        {
            if (ValidatePath(path))
            {
                currentDocument = new XDocument(
                    new XDeclaration("1.0", "utf-8", "yes"),
                    new XElement("Algorithm", 
                        new XElement("Header"),
                        new XElement("Parameters"))
                    );
                currentDocument.Save(path);
                IsLoaded = true;
            }
            else
            {
                throw new Exception(Resources.XMLParser_Incorrect_Extension);
            }
        }

        /// <summary>
        /// Проверяет соответствие файла формату ХМЛ.
        /// </summary>
        /// <param name="path">Путь к файлу.</param>
        private static bool ValidatePath(string path)
        {
            return path.EndsWith(".xml");
        }
        #endregion // Methods

        
    }
}
